#include "stdafx.h"
#include "AVPlayer.h"
#include "BufferCache.h"
#include "PacketQueue.h"

#include <fstream>

#define __STDC_CONSTANT_MACROS

#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avdevice.lib")
#pragma comment(lib, "avfilter.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "postproc.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "swscale.lib")

#ifdef _WIN64
#else
#pragma comment(lib, "x86/SDL2.lib")
#pragma comment(lib, "x86/SDL2main.lib")
#endif

#define _SUPPORT_AUDIO_

//Refresh Event
#define SFM_REFRESH_VIDEO_EVENT  (SDL_USEREVENT + 1)  

#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)  

extern "C"
{

#include "libavcodec\avcodec.h"
#include "libavformat\avformat.h"
#include "libavutil\error.h"
#include "libavutil\imgutils.h"
#include "libswscale\swscale.h"
#include "libswresample/swresample.h"

#include "SDL.h"
}

#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio


typedef enum{player_play, player_stop, player_pause}PlayerState;



//Buffer:
//|-----------|-------------|
//chunk-------pos---len-----|
static  Uint8  *audio_chunk;
static  Uint32  audio_len;
static  Uint8  *audio_pos;

/* The audio function callback takes the following parameters:
* stream: A pointer to the audio buffer to be filled
* len: The length (in bytes) of the audio buffer
*/
void  fill_audio(void *udata, Uint8 *stream, int len) {
	//SDL 2.0
	SDL_memset(stream, 0, len);
	if (audio_len == 0)
		return;

	len = (len>audio_len ? audio_len : len);	/*  Mix  as  much  data  as  possible  */

	SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);
	audio_pos += len;
	audio_len -= len;
}

class CAVPlayer : public IAVPlayer
{
public:
	CAVPlayer();
	virtual ~CAVPlayer();

	virtual bool		initPlayer(void* parentWindow = nullptr, const unsigned long& uInitFlag = 0);
	virtual bool		openMedia(const char* lpszMediaPath);
	virtual bool		play();
	virtual bool		stop();
	virtual bool		pause();
	virtual int			wait();
private:
	void				dumpMediaInfo();
	static int			playThreadFun(void* pCustomData);

	void				decodeToFile(const char* lpszPCMFile);

	static int			playLoadPacketFun(void* pCustomData);
	SDL_Thread*			m_pLoadPacketThread;
	//audio
	AVStream*			m_pAudioStream;
	AVCodecContext*		m_pAudioCodecCtx;
	AVCodec*			m_pAudioCodec;
	CPacketQueue		m_audioPacketQueue;

	static int			playAudioThreadFun(void* pCustomData);
	SDL_Thread*			m_pPlayAudioThread;
	static void			fillAudio(void* pUserData, Uint8* stream, int len);
	unsigned int		m_uAudioIndex;

	//video
	AVStream*			m_pVideoStream;
	AVCodecContext*		m_pVideoCodecCtx;
	AVCodec*			m_pVideoCodec;
	CPacketQueue		m_VideoPacketQueue;

	static int			playVideoThreadFun(void* pCustomData);
	SDL_Thread*			m_pPlayVideoThread;
	CPacketQueue		m_videoPacketQueue;

	unsigned int		m_uVideoIndex;
	SDL_TimerID			m_sdlVideoRefreshTimerID;
	static Uint32		videoRefreshTimer(Uint32 interval, void *param);

	SDL_Window*			m_pVideoWnd;
	SDL_Renderer*		m_pRender;
	SDL_Rect			m_rcVideo;

private:

	CBufferCache		m_bufCacheAry[2];
	unsigned short		m_uReaderIndex;
	unsigned short		m_uWriteIndex;


private:
	bool		m_bAlreadyInit;

	AVFormatContext*	m_pFormatCtx;


	PlayerState			m_playerState;

	SDL_Thread*			m_pPlayThread;

	std::string			m_strMediaPath;

	void*			m_parentWindow;
};

void CAVPlayer::fillAudio(void * pUserData, Uint8 * stream, int len)
{
	assert(pUserData);
	CAVPlayer* pPlayer = (CAVPlayer*)pUserData;
	//SDL 2.0
	SDL_memset(stream, 0, len);
	auto pBufCache = &pPlayer->m_bufCacheAry[pPlayer->m_uReaderIndex];
	auto dataLen = pBufCache->getRemainDataSize();
	if (dataLen == 0)
		return;

	len = (len>dataLen ? dataLen : len);	/*  Mix  as  much  data  as  possible  */

	SDL_MixAudio(stream, pBufCache->readBuf(len), len, SDL_MIX_MAXVOLUME);
	//audio_pos += len;
	//audio_len -= len;
}

int CAVPlayer::playVideoThreadFun(void * pCustomData)
{
	CAVPlayer* pPlayer = (CAVPlayer*)pCustomData;
	auto& vidowPacketQ = pPlayer->m_videoPacketQueue;
	int nNextPacket = 0;

	AVFrame* pFrameVideo = av_frame_alloc();
	if (!pFrameVideo)
		return 0;
	ON_SCOPE_EXIT([&pFrameVideo]() {av_frame_free(&pFrameVideo); });
	AVFrame* pFrameYUV = av_frame_alloc();
	if (!pFrameYUV)
		return 0;
	ON_SCOPE_EXIT([&pFrameYUV]() {av_frame_free(&pFrameYUV); });

	auto pFormatCtx = pPlayer->m_pFormatCtx;

	auto pVideoCodecCtx = pPlayer->m_pVideoCodecCtx;

	int nBufSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pVideoCodecCtx->width, pVideoCodecCtx->height, 1);
	auto outBuf = (unsigned char*)av_malloc(nBufSize);
	av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuf, AV_PIX_FMT_YUV420P, pVideoCodecCtx->width, pVideoCodecCtx->height, 1);

	//av_dump_format(pFormatCtx, 0, lpszMediaPath, 0);

	SwsContext* img_convert_ctx = sws_getContext(pVideoCodecCtx->width, pVideoCodecCtx->height, pVideoCodecCtx->pix_fmt
		, pVideoCodecCtx->width, pVideoCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, nullptr, nullptr, nullptr);
	ON_SCOPE_EXIT([&img_convert_ctx]() {sws_freeContext(img_convert_ctx); });

	SDL_Texture* pTexture = SDL_CreateTexture(pPlayer->m_pRender, SDL_PIXELFORMAT_IYUV
		, SDL_TEXTUREACCESS_STREAMING, pVideoCodecCtx->width, pVideoCodecCtx->height);

	SDL_Event sdlEvent;
	while (pPlayer->m_playerState != player_stop)
	{
		SDL_WaitEvent(&sdlEvent);

		switch (sdlEvent.type)
		{
		case SFM_REFRESH_VIDEO_EVENT:
		{
			if (!vidowPacketQ.isEmpty())
			{
				auto pVideoPacket = vidowPacketQ.popFront();
				assert(pVideoPacket);
				if (pVideoPacket != nullptr)
				{
					ON_SCOPE_EXIT([&pVideoPacket]() {av_packet_free(&pVideoPacket); pVideoPacket = nullptr; });
					int nGotPitcture = 0;
					if (avcodec_decode_video2(pVideoCodecCtx, pFrameVideo, &nGotPitcture, pVideoPacket) < 0)
					{
						pPlayer->stop();
						return 0;
					}

					if (nGotPitcture)
					{
						sws_scale(img_convert_ctx, pFrameVideo->data, pFrameVideo->linesize, 0, pVideoCodecCtx->height
							, pFrameYUV->data, pFrameYUV->linesize);

						SDL_UpdateTexture(pTexture, nullptr, pFrameYUV->data[0], pFrameYUV->linesize[0]);
						SDL_RenderClear(pPlayer->m_pRender);
						SDL_RenderCopy(pPlayer->m_pRender, pTexture, nullptr, &pPlayer->m_rcVideo);
						SDL_RenderPresent(pPlayer->m_pRender);
					}
				}
			}
		}
		break;
		}
		if (audio_len > 0)
			SDL_Delay(10);
	}
}

Uint32 CAVPlayer::videoRefreshTimer(Uint32 interval, void * param)
{
	CAVPlayer* pPlayer = (CAVPlayer*)param;
	assert(pPlayer);

	if (pPlayer->m_playerState == player_play)
	{
		SDL_Event event;
		event.type = SFM_REFRESH_VIDEO_EVENT;
		SDL_PushEvent(&event);
	}
	return interval;
}

CAVPlayer::CAVPlayer()
	: m_bAlreadyInit(false)
	, m_pFormatCtx(nullptr)
	, m_uAudioIndex(-1)
	, m_pAudioStream(nullptr)
	, m_pAudioCodecCtx(nullptr)
	, m_pAudioCodec(nullptr)
	, m_pPlayThread(nullptr)
	, m_audioPacketQueue(100)
	, m_pPlayAudioThread(nullptr)
	, m_pLoadPacketThread(nullptr)
	, m_parentWindow(nullptr)
{
}

CAVPlayer::~CAVPlayer()
{
	if (m_pFormatCtx)
	{
		avformat_close_input(&m_pFormatCtx);
		avformat_free_context(m_pFormatCtx);
	}
}

bool CAVPlayer::initPlayer(void* parentWindow /*= nullptr*/, const unsigned long& uInitFlag/* = 0*/)
{
	if (!m_bAlreadyInit)
	{
		av_register_all();
		auto sdlInitFlag = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
		SDL_Init(sdlInitFlag);
		m_parentWindow = parentWindow;
		m_pVideoWnd = SDL_CreateWindowFrom(m_parentWindow);
		m_pRender = SDL_CreateRenderer(m_pVideoWnd, -1, 0);
		m_rcVideo.x = 0;
		m_rcVideo.y = 0;
		SDL_GetWindowSize(m_pVideoWnd, &m_rcVideo.w, &m_rcVideo.h);
	}
	return true;
}

bool CAVPlayer::openMedia(const char * lpszMediaPath)
{
	if (!lpszMediaPath)
		return false;
	m_strMediaPath = lpszMediaPath;

	m_pFormatCtx = avformat_alloc_context();
	if (!m_pFormatCtx)
		return false;

	//open video file
	auto nRetCode = avformat_open_input(&m_pFormatCtx, lpszMediaPath, nullptr, nullptr);
	if (nRetCode < 0)
		return false;

	//find stream info
	nRetCode = avformat_find_stream_info(m_pFormatCtx, nullptr);
	if (nRetCode < 0)
		return false;

	//find video stram index
	m_uAudioIndex = (unsigned int)-1;
	m_uVideoIndex = (unsigned int)-1;
	for (unsigned int i = 0; i < m_pFormatCtx->nb_streams; ++i)
	{
		if (m_pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
			m_uAudioIndex = i;
		else if (m_pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
			m_uVideoIndex = i;
	}
	if (m_uAudioIndex == (unsigned int)-1 && m_uVideoIndex == (unsigned int)-1)
		return false;

	//finde decoder
	
	//audio
	if (m_uAudioIndex != (unsigned int)-1)
	{
		m_pAudioStream = m_pFormatCtx->streams[m_uAudioIndex];
		m_pAudioCodecCtx = m_pAudioStream->codec;
		m_pAudioCodec = avcodec_find_decoder(m_pAudioCodecCtx->codec_id);
		if (!m_pAudioCodec)
			return false;

		if (avcodec_open2(m_pAudioCodecCtx, m_pAudioCodec, nullptr) < 0)
			return false;
	}

	//video
	if (m_uVideoIndex != (unsigned int)-1)
	{
		m_pVideoStream = m_pFormatCtx->streams[m_uVideoIndex];
		m_pVideoCodecCtx = m_pVideoStream->codec;
		m_pVideoCodec = avcodec_find_decoder(m_pVideoCodecCtx->codec_id);
		if (!m_pVideoCodec)
			return false;

		if (avcodec_open2(m_pVideoCodecCtx, m_pVideoCodec, nullptr) < 0)
			return false;
	}

	dumpMediaInfo();
	return true;
}

bool CAVPlayer::play()
{
	//decodeToFile("Z:\\test.pcm");
	//return true;
	m_playerState = player_play;
	m_pLoadPacketThread = SDL_CreateThread(&CAVPlayer::playLoadPacketFun, nullptr, this);
	m_pPlayAudioThread = SDL_CreateThread(&CAVPlayer::playAudioThreadFun, nullptr, this);
	m_pPlayVideoThread = SDL_CreateThread(&CAVPlayer::playVideoThreadFun, nullptr, this);
	m_sdlVideoRefreshTimerID = SDL_AddTimer(40, &CAVPlayer::videoRefreshTimer, this);

	m_pPlayThread = SDL_CreateThread(&CAVPlayer::playThreadFun, nullptr, this);
	return true;
}

bool CAVPlayer::stop()
{
	m_playerState = player_stop;
	return true;
}

bool CAVPlayer::pause()
{
	m_playerState = player_pause;
	return true;
}

int CAVPlayer::wait()
{
	int nState = 0;
	SDL_WaitThread(m_pPlayThread, &nState);
	return 0;
}

void CAVPlayer::dumpMediaInfo()
{
	av_dump_format(m_pFormatCtx, 0, m_strMediaPath.c_str(), false);
}

int CAVPlayer::playThreadFun(void * pCustomData)
{
	CAVPlayer* pPlayer = (CAVPlayer*)pCustomData;
	assert(pPlayer);
	SDL_Event sdlEvent;
	while (pPlayer->m_playerState != player_stop)
	{
		//SDL_WaitEvent(&sdlEvent);

		//switch (sdlEvent.type)
		//{
		//case SDL_QUIT:
		//{
		//	pPlayer->stop();
		//	SDL_CloseAudio();
		//	SDL_Quit();
		//}
		//break;
		//case SFM_BREAK_EVENT:
		//	return 0;
		//	break;
		//}
		SDL_Delay(100);
	}
	SDL_CloseAudio();
	SDL_Quit();

	return 0;
}

void CAVPlayer::decodeToFile(const char * lpszPCMFile)
{
	std::ofstream ofstream;
	ofstream.open(lpszPCMFile, std::ios_base::binary | std::ios_base::out);

	CAVPlayer* pPlayer = this;
	assert(pPlayer);

	auto pFormatCtx = pPlayer->m_pFormatCtx;

	auto pAudioCodecCtx = pPlayer->m_pAudioCodecCtx;
	
	//Out Audio Param
	uint64_t out_channel_layout = pAudioCodecCtx->channel_layout;
	//nb_samples: AAC-1024 MP3-1152
	int out_nb_samples = pAudioCodecCtx->frame_size;
	AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S32;
	int out_sample_rate = pAudioCodecCtx->sample_rate;
	int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
	//Out Buffer Size
	int out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 0);

	auto out_buffer = (uint8_t *)av_malloc(out_buffer_size);

	auto pAudioFrame = av_frame_alloc();

	auto in_channel_layout = av_get_default_channel_layout(pAudioCodecCtx->channels);
	SwrContext* pAudioConvertCtx = swr_alloc();
	pAudioConvertCtx = swr_alloc_set_opts(pAudioConvertCtx, out_channel_layout, out_sample_fmt, out_sample_rate,
		in_channel_layout, pAudioCodecCtx->sample_fmt, pAudioCodecCtx->sample_rate, 0, NULL);
	swr_init(pAudioConvertCtx);

	int i = 0;
	while (true)
	{
		{
			auto pAudioPacket = (AVPacket*)av_malloc(sizeof(AVPacket));

			if (av_read_frame(pFormatCtx, pAudioPacket) < 0)
			{
				break;
			}
			if (pAudioPacket->stream_index == pPlayer->m_uAudioIndex)
			{
				int nGotPitcture = 0;
				auto nLen = avcodec_decode_audio4(pAudioCodecCtx, pAudioFrame, &nGotPitcture, pAudioPacket);
				assert(nLen == pAudioPacket->size);
				if (nLen > 0 && nGotPitcture > 0)
				{
					assert(nGotPitcture == 1);
					//swr_convert(pAudioConvertCtx, &out_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t**)pAudioFrame->data, pAudioFrame->nb_samples);
					swr_convert(pAudioConvertCtx, &out_buffer, out_buffer_size, (const uint8_t**)pAudioFrame->data, pAudioFrame->nb_samples);
					ofstream.write((const char*)out_buffer, out_buffer_size);
					/*ofstream.write((const char*)pAudioFrame->data[0], pAudioFrame->linesize[0]);
					ofstream.write((const char*)pAudioFrame->data[1], pAudioFrame->linesize[0]);*/
				}
			}
			av_packet_free(&pAudioPacket);
		}
	}
	ofstream.close();
}

int CAVPlayer::playLoadPacketFun(void * pCustomData)
{
	CAVPlayer* pPlayer = (CAVPlayer*)pCustomData;
	assert(pPlayer);

	auto pFormatCtx = pPlayer->m_pFormatCtx;
	auto pAudioCodecCtx = pPlayer->m_pAudioCodecCtx;

	auto& audioPacketQ = pPlayer->m_audioPacketQueue;
	auto& videoPacketQ = pPlayer->m_videoPacketQueue;

	bool bWait = false;
	while (true)
	{
		if (bWait)
		{
			SDL_Delay(100);
			if (audioPacketQ.getSize() < audioPacketQ.getMaxSize() / 3)
				bWait = false;
		}
		else
		{
			if (audioPacketQ.isFull())
			{
				bWait = true;
			}
			else
			{
				auto pPacket = (AVPacket*)av_malloc(sizeof(AVPacket));

				if (av_read_frame(pFormatCtx, pPacket) < 0)
				{
					//pPlayer->m_bAlreadyEnd = true;
					break;
				}
				if (pPacket->stream_index == pPlayer->m_uAudioIndex)
				{
					audioPacketQ.push(pPacket);
				}
				else if (pPacket->stream_index == pPlayer->m_uVideoIndex)
				{
					videoPacketQ.push(pPacket);
				}
				else
				{
					av_packet_free(&pPacket);
				}
			}
		}
	}
	return 0;
}

int CAVPlayer::playAudioThreadFun(void * pCustomData)
{
	CAVPlayer* pPlayer = (CAVPlayer*)pCustomData;
	assert(pPlayer);

	auto pFormatCtx = pPlayer->m_pFormatCtx;

	int nNextPacket = 0;

	int nAudioNextPacket = 0;
	auto pAudioCodecCtx = pPlayer->m_pAudioCodecCtx;

	//Out Audio Param
	uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;
	//nb_samples: AAC-1024 MP3-1152
	int out_nb_samples = pAudioCodecCtx->frame_size;

	auto getDestSampleFmt = [](const AVSampleFormat& srcSampleFmt)->AVSampleFormat
	{
		AVSampleFormat tgtSampleFmt = srcSampleFmt;
		if (srcSampleFmt >= AV_SAMPLE_FMT_NB || srcSampleFmt <= AV_SAMPLE_FMT_NONE)
			tgtSampleFmt = AV_SAMPLE_FMT_S16;
		else if (srcSampleFmt > AV_SAMPLE_FMT_DBL)
		{
			tgtSampleFmt = (AVSampleFormat)(srcSampleFmt - 5);
			//if (tgtSampleFmt > AV_SAMPLE_FMT_S32)
			//	tgtSampleFmt = AV_SAMPLE_FMT_S32;
		}

		return tgtSampleFmt;
	};
	AVSampleFormat out_sample_fmt = getDestSampleFmt(pAudioCodecCtx->sample_fmt);
	
	int out_sample_rate = pAudioCodecCtx->sample_rate;
	int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
	//Out Buffer Size
	int out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 0);

	auto out_buffer = (uint8_t *)av_malloc(out_buffer_size);

	auto pAudioFrame = av_frame_alloc();

	auto getSDLAudioFmt = [](const AVSampleFormat& avSampleFmt)->SDL_AudioFormat
	{
		SDL_AudioFormat fmt;
		switch (avSampleFmt)
		{
		case AV_SAMPLE_FMT_U8:
			fmt = AUDIO_U8;
			break;
		case AV_SAMPLE_FMT_S16:
			fmt = AUDIO_S16;
			break;
		case AV_SAMPLE_FMT_S32:
			fmt = AUDIO_S32;
			break;
		case AV_SAMPLE_FMT_DBL:
		case AV_SAMPLE_FMT_FLT:
			fmt = AUDIO_F32SYS;
			break;
		default:
			assert(false);
			return 0;
		}
		return fmt;
	};

	SDL_AudioSpec sdlAudioSpec;
	sdlAudioSpec.freq = out_sample_rate;
	sdlAudioSpec.format = getSDLAudioFmt(out_sample_fmt);
	sdlAudioSpec.channels = out_channels;
	sdlAudioSpec.silence = 0;
	sdlAudioSpec.samples = out_nb_samples;
	sdlAudioSpec.callback = &fill_audio;
	sdlAudioSpec.userdata = pPlayer;

	if (SDL_OpenAudio(&sdlAudioSpec, NULL)<0) {
		std::cout << "can't open audio." << std::endl;
		return -1;
	}

	auto in_channel_layout = av_get_default_channel_layout(pAudioCodecCtx->channels);
	SwrContext* pAudioConvertCtx = swr_alloc();
	pAudioConvertCtx = swr_alloc_set_opts(pAudioConvertCtx, out_channel_layout, out_sample_fmt, out_sample_rate,
		in_channel_layout, pAudioCodecCtx->sample_fmt, pAudioCodecCtx->sample_rate, 0, NULL);
	swr_init(pAudioConvertCtx);

	SDL_PauseAudio(0);
	auto& audioPacketQ = pPlayer->m_audioPacketQueue;
	int i = 0;
	while (true)
	{
		if (audio_len > 0)
		{
			SDL_Delay(1);
			//continue;
		}
		else if (!audioPacketQ.isEmpty())
		{
			auto pAudioPacket = audioPacketQ.popFront();
			assert(pAudioPacket);

			//if (pAudioPacket->stream_index == pPlayer->m_uAudioIndex)
			{
				int nGotPitcture = 0;
				auto nLen = avcodec_decode_audio4(pAudioCodecCtx, pAudioFrame, &nGotPitcture, pAudioPacket);
				assert(nLen == pAudioPacket->size);
				if (nLen < 0)
				{
					pPlayer->stop();
					return 0;
				}

				if (nGotPitcture)
				{
					assert(nGotPitcture == 1);
					auto ddd = swr_get_out_samples(pAudioConvertCtx, pAudioFrame->nb_samples);
					auto bufSIzeOutPerChanel = swr_convert(pAudioConvertCtx, &out_buffer, out_buffer_size
						, (const uint8_t **)pAudioFrame->data, pAudioFrame->nb_samples);

					//Set audio buffer (PCM data)
					audio_chunk = (Uint8 *)out_buffer;
					//Audio buffer length
					audio_len = out_buffer_size;
					audio_pos = audio_chunk;

					//SDL_PauseAudio(0);
				}
			}
			av_packet_free(&pAudioPacket);
		}
	}
	return 0;
}

IAVPlayer * __stdcall CreateAVPlayer()
{
	return new CAVPlayer();
}
